package com.zkh.myutils.markup;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

import com.zkh.myutils.utils.StringUtils;
import com.zkh.myutils.utils.Utils;

/**
 * 查找器
 */
class SelectorFindor {
	
	/**
	 * 选择器分隔符
	 */
	private final static List<Character> SELECTOR_SEPARATORS = Arrays.asList(' ', '.', '#', '>', '+', '~', ':', '(', '[');
	/**
	 * 选择器缓存映射
	 */
	private final static Map<String, List<String>> SELECTOR_CACHE_MAP = new HashMap<>();
	
	/**
	 * 根据选择器查找元素
	 * @param element 顶级元素
	 * @param selector 选择器
	 * @param cacheSelector 是否缓存解析后的选择器
	 * @return 查找到的元素集合
	 */
	public static List<Element> find(Element element, String selector, boolean cacheSelector) {
		//筛选集合和结果集合
		List<Element> qryList = new ArrayList<>();
		//按==去重，不重写equals
		LinkedHashSet<Element> allResSet = new LinkedHashSet<>();
		//拆分遍历
		for(String s: selector.split(",")) {
			//临时结果集
			LinkedHashSet<Element> resSet = new LinkedHashSet<>(Collections.singleton(element));
			//不为空
			if(!(s = s.trim()).isEmpty()) {
				//解析选择器 [' ', '.', '#', '>', '+', '~', ':', '(', '[' ]
				List<String> selectors = parseSelector(s, cacheSelector);
				//当前操作符
				char c;
				//查找元素
				for(int i=0,len=selectors.size(); i<len; i++) {
					//将查询结果作为待查询项
					qryList.addAll(resSet); resSet.clear();
					//当前
					String curr = selectors.get(i);
					//判断操作
					switch (c = curr.charAt(0)) {
						case '>':
						case ' ': {
							//递归标识
							boolean recursive = c==' ';
							//下一个选择器
							String nc = selectors.get(++i);
							//开始符号
							char t = nc.charAt(0);
							//类或ID
							boolean ci = t=='.' || t=='#';
							//偏移索引
							int off = ci ? 1 : 0;
							//通配符*
							if("*".equals(nc)) {
								eachQryElement(qryList, e->findAllChild(e, resSet, recursive));
							}//检查下一选择器是否是“:”符号
							else if(":".equals(get(selectors, i+off+1))) {
								//获取“:”后的值
								String nc2 = selectors.get(i+off+2);
								//查找类型
								int type = t=='.' ? 1 : (t=='#' ? 2 : 3);
								String value = ci ? selectors.get(i+1) : nc;
								//获取nth-child的位置
								String place;
								//指定位置
								if("nth-child".equals(nc2)) {
									place = selectors.get(i+off+4);
									i = i + off + 4;
								}else {
									place = null;
									i = i + off + 2;
								}
								//开始处理
								eachQryElement(qryList, e->{
									//非文本元素，则处理
									if(!e.isText()) {
										findChildByNth(e, type, value, nc2, place, resSet, recursive);
									}
								});
							}//正常解析
							else {
								//类或ID
								if(ci) {
									//下个选择器（id或class的值）
									String nc1 = selectors.get(++i);
									//开始查找
									eachQryElement(qryList, e->findChildByAttr(e, t=='.' ? "class" : "id", nc1, resSet, recursive));
								}//标签
								else {
									eachQryElement(qryList, e->findChildByTagName(e, nc, resSet, recursive));
								}
							}
						} break;
						case '.':
						case '#': {
							//属性
							String attrKey = c=='.' ? "class" : "id";
							String attrValue = selectors.get(++i);
							//遍历查找
							eachQryElement(qryList, e->{
								//属性值
								String v = e.getAttr(attrKey);
								//当前元素
								if(!e.isText() && ("class".equals(attrKey) ? isClassMatched(v, attrValue) : attrValue.equals(v))) {
									resSet.add(e);
								}
							});
						} break;
						case '+': {
							//下一个选择器
							String nc = selectors.get(++i);
							//开始符号
							char t = nc.charAt(0);
							//属性选择（类或ID）
							boolean attr = t=='.' || t=='#';
							//最终选择器
							String nc1 = attr ? selectors.get(++i) : nc;
							//开始查询
							eachQryElement(qryList, e->{
								//下一个元素
								Element next = e.next();
								//不为null且匹配
								if(next!=null && !next.isText()) {
									//通配符
									if("*".equals(nc1)) {
										resSet.add(next);
									}//class
									else if(t=='.' ? isClassMatched(next.getAttr("class"), nc1)
											: (attr ? nc1.equals(next.getAttr("id")) : nc1.equalsIgnoreCase(next.getName()))) {
										resSet.add(next);
									}
								}
							});
						} break;
						case '~': {
							//下一个选择器
							String nc = selectors.get(++i);
							//开始符号
							char t = nc.charAt(0);
							//属性选择（类或ID）
							boolean attr = t == '.' || t == '#';
							//最终选择器
							String nc1 = attr ? selectors.get(++i) : nc;
							//通配符*
							boolean star = "*".equals(nc1);
							//开始查找
							eachQryElement(qryList, e->{
								//父级元素
								Element p = e.getParent();
								//有父级，获取兄弟元素
								if(p != null) {
									//兄弟元素
									List<Element> cs = p.getChildren();
									//遍历
									for(int j=0,jlen=cs.size(); j<jlen; j++) {
										//找到当前元素
										if(cs.get(j)==e) {
											//继续遍历后续
											for(j=j+1; j<jlen; j++) {
												//当前元素
												Element elem = cs.get(j);
												//不为null且匹配
												if(elem!=null && !elem.isText()) {
													//通配符
													if(star) {
														resSet.add(elem);
													}//class
													else if(t=='.' ? isClassMatched(elem.getAttr("class"), nc1)
															: (attr ? nc1.equals(elem.getAttr("id")) : nc1.equalsIgnoreCase(elem.getName()))) {
														resSet.add(elem);
													}
												}
											}
										}
									}
								}
							});
						} break;
						case '[': {
							//下一个选择器
							String nc = selectors.get(++i);
							//拆分属性和值
							int idx = nc.indexOf('=');
							String attrKey = idx==-1 ? nc : nc.substring(0, idx);
							String attrValue = idx==-1 ? null : nc.substring(idx + 1);
							//遍历
							eachQryElement(qryList, e->{
								if(attrValue==null ? e.hasAttr(attrKey) : attrValue.equals(e.getAttr(attrKey))) {
									resSet.add(e);
								}
							});
						} break;
						default:
							//*号
							boolean start = c=='*';
							//遍历查找
							eachQryElement(qryList, e->{
								if(start) {
									//非文本节点
									if(!e.isText()) {
										resSet.add(e);
									}
									findAllChild(e, resSet, true);
								}else {
									//判断当前元素
									if(!e.isText() && curr.equalsIgnoreCase(e.getName())) {
										resSet.add(e);
									}
									//查找子元素
									findChildByTagName(e, curr, resSet);
								}
							});
					}
					//结果容器清零
					qryList.clear();
				}
				//保存结果
				allResSet.addAll(resSet);
			}
		}
		//返回结果
		return new ArrayList<>(allResSet); 
	}
	
	/**
	 * 遍历查询集合
	 * @param qryList 查询集合
	 * @param consumer 自定义处理方法
	 */
	private static void eachQryElement(List<Element> qryList, Consumer<Element> consumer) {
		qryList.forEach(consumer);
	}

	/**
	 * 根据属性值查询子元素
	 * @param elem 要查找元素的父节点
	 * @param attrName 属性名
	 * @param attrValue 属性值。属性值为null时，直接判断存在属性名
	 * @param resSet 结果容器
	 * @param recursion 是否递归查询
	 */
	private static void findChildByAttr(Element elem, String attrName, String attrValue, LinkedHashSet<Element> resSet, boolean recursion) {
		//遍历子元素
		for(Element t: elem.getChildren()) {
			//非文本元素，则处理
			if(!t.isText()) {
				//class匹配
				if("class".equals(attrName) && attrValue!=null) {
					//匹配到class
					if(isClassMatched(t.getAttr(attrName), attrValue)) {
						resSet.add(t);
					}
				}
				//没有属性值，默认查询含指定属性名的元素
				else if(attrValue==null ? t.hasAttr(attrName) : attrValue.equals(t.getAttr(attrName))) {
					resSet.add(t);
				}
				if(recursion) {
					//继续处理子元素
					findChildByAttr(t, attrName, attrValue, resSet, true);
				}
			}
		}
	}
	
	/**
	 * 检查class值是否匹配class属性值
	 * @param classAttrValue class属性值
	 * @param match 待匹配的class值
	 * @return 匹配到返回true，否则返回false
	 */
	private static boolean isClassMatched(String classAttrValue, String match) {
		//为空直接返回匹配失败
		if(StringUtils.isEmpty(classAttrValue)) {
			return false;
		}
		//匹配class
		for(String c: classAttrValue.split("\\s+")) {
			if(match.equals(c)) {
				return true;
			}
		}
		//未匹配到
		return false;
	}

	/**
	 * 根据标签名称查询节点
	 * @param elem 要查找元素的父节点
	 * @param tagName 标签名
	 * @param resSet 结果容器
	 */
	private static void findChildByTagName(Element elem, String tagName, LinkedHashSet<Element> resSet) {
		findChildByTagName(elem, tagName, resSet, true);
	}

	/**
	 * 根据标签名称查询节点
	 * @param elem 要查找元素的父节点
	 * @param tagName 标签名
	 * @param resSet 结果容器
	 * @param recursion 是否递归查询
	 */
	private static void findChildByTagName(Element elem, String tagName, LinkedHashSet<Element> resSet, boolean recursion) {
		//遍历查询
		for(Element t: elem.getChildren()) {
			//非文本元素，则处理
			if(!t.isText()) {
				//判断标签名
				if(tagName.equalsIgnoreCase(t.getName())) {
					resSet.add(t);
				}
				if(recursion) {
					//继续处理子元素
					findChildByTagName(t, tagName, resSet, true);
				}
			}
		}
	}
	
	/**
	 * 查找所有子元素
	 * @param elem 父元素
	 * @param resSet 结果容器
	 * @param recursive 是否递归处理子元素
	 */
	private static void findAllChild(Element elem, LinkedHashSet<Element> resSet, boolean recursive) {
		//遍历查询
		for(Element t: elem.getChildren()) {
			//非文本元素，则处理
			if(!t.isText()) {
				//保存元素
				resSet.add(t);
				//递归处理子元素
				if(recursive) {
					//继续处理子元素
					findAllChild(t, resSet, true);
				}
			}
		}
	}
	
	/**
	 * 通过:first-child、:last-child或:nth-child()获取元素
	 * @param elem 父级元素
	 * @param type 类型。1：class，2：id，3：标签
	 * @param value 类型值，对应class值、id值和标签名
	 * @param thDesc “:”选择器的描述，取值有：first-child、last-child、nth-child
	 * @param place 当value为nth-child时，用来指定位置的字符串
	 * @param resSet 结果容器
	 * @param recursive 是否递归调用
	 */
	private static void findChildByNth(Element elem, int type, String value, String thDesc, 
			String place, LinkedHashSet<Element> resSet, boolean recursive) {
		//判断元素是否符合要求
		Function<Element, Boolean> matches = e->{
			//为空
			if(e==null) {
				return false;
			}
			//class
			boolean matched = type==1 && isClassMatched(e.getAttr("class"), value);
			//ID
			matched = matched || (type==2 && value.equals(e.getAttr("id")));
			//标签名
			return matched || (type==3 && value.equalsIgnoreCase(e.getName()));
		};
		//子节点
		List<Element> cs = elem.getChildren();
		//子节点数量
		int len = cs.size();
		//first-child
		if("first-child".equals(thDesc)) {
			//开头元素
			Element firstChild = elem.firstChild();
			//检查
			if(matches.apply(firstChild)) {
				resSet.add(firstChild);
			}
		}//last-child
		else if("last-child".equals(thDesc)) {
			//结尾元素
			Element lastChild = elem.lastChild();
			//检查
			if(matches.apply(lastChild)) {
				resSet.add(lastChild);
			}
		}//nth-child
		else if("nth-child".equals(thDesc)) {
			//位置是数字，如：nth-child(2)
			if(StringUtils.isNumber(place)) {
				//转索引
				int idx = Integer.parseInt(place)-1;
				//符合条件，取对应元素
				if(matches.apply(get(cs, idx))) {
					resSet.add(cs.get(idx));
				}
			}//位置，如：nth-child(2n)、nth-child(3n-1)、nth-child(4n+2)
			else {
				//获取n字符位置
				int ni = place.indexOf('n');
				//倍数（值可能为-1）
				int b = Integer.parseInt(place.substring(0, ni));
				//偏移方向
				int d = place.length()>ni+1 ? (place.charAt(ni+1)=='+' ? 1 : -1) : 0;
				//偏移量
				int off = d!=0 ? Integer.parseInt(place.substring(ni+2)) : 0;
				//取范围
				boolean r = b==1 || b==-1;
				//直接遍历子元素
				for(int k=0; k<len; k++) {
					//取范围
					if(r) {
						int idx = b * (k+1) + off - 1;
						//存在则保存
						if(idx>-1 && idx<len) {
							resSet.add(cs.get(idx));
						}
					}else {
						if((k+1 + d * off) % b ==0) {
							resSet.add(cs.get(k));
						}
					}
				}
			}
		}
		//如果递归查询
		if(recursive) {
			for(Element t: elem.getChildren()) {
				//非文本元素，则处理
				if(!t.isText()) {
					//继续处理子元素
					findChildByNth(t, type, value, thDesc, place, resSet, true);
				}
			}
		}
	}
	
	/**
	 * 获取集合元素，超出索引返回null
	 * @param list 集合
	 * @param index 索引
	 */
	private static <T> T get(List<T> list, int index) {
		try {
			return list.get(index);
		}catch (IndexOutOfBoundsException e) {
			return null;
		}
	}

	/**
	 * 解析选择器
	 * @param selector 选择器字符串
	 * @param cacheSelector 是否缓存解析后的选择器
	 * @return 选择器列表
	 */
	private static List<String> parseSelector(String selector, boolean cacheSelector){
		//不缓存，直接解析
		if(!cacheSelector) {
			return parseSelector(selector);
		}
		//获取缓存
		List<String> selectorList = SELECTOR_CACHE_MAP.get(selector);
		//不存在
		if(selectorList==null) {
			//加锁解析
			synchronized (selector.intern()) {
				//如果值仍为空
				if((selectorList = SELECTOR_CACHE_MAP.get(selector))==null) {
					//解析并保存缓存
					SELECTOR_CACHE_MAP.put(selector, (selectorList = parseSelector(selector)));
				}
			}
		}
		//返回选择器列表
		return selectorList;
	}

	/**
	 * 解析选择器
	 * @param selector 选择器字符串
	 * @return 选择器列表
	 */
	private static List<String> parseSelector(String selector){
		//转数组
		char[] chars = selector.toCharArray();
		//临时字符
		List<Character> charList = new ArrayList<>();
		//结果批次
		List<String> batchList = new ArrayList<>();
		//解析
		for(int i=0,len=chars.length; i<len; i++) {
			//当前字符
			char c = chars[i];
			//当前是空格
			if(c<=' ') {
				//前面是空格，跳过
				if(i>0 && chars[i-1]<=' ') {
					continue;
				}
				//保存批次
				if(!charList.isEmpty()) {
					batchList.add(Utils.join("", charList));
				}
				batchList.add(" ");
				//清空容器
				charList.clear();
			}else {
				//伪类选择器:nth-child(2)和属性选择器
				if(c=='(' || c=='[') {
					//开始和闭合
					char start = c, close = c=='(' ? ')' : ']';
					//结束前面内容
					batchList.add(Utils.join("", charList));
					charList.clear();
					//新的操作
					batchList.add(String.valueOf(c));
					boolean find = false;
					//查找结束
					for(i=i+1; i<len; i++) {
						//当前字符
						c = chars[i];
						//空格
						if(c<=' ') continue;
						//结束
						if(c==close) {
							find = true;
							break;
						}
						//保存
						charList.add(c);
					}
					if(!find) {
						throw new MarkupException("选择器格式错误，“" + start + "”符号没有闭合");
					}
					//结束
					batchList.add(Utils.join("", charList));
					charList.clear();
				}//分隔符
				else if(SELECTOR_SEPARATORS.contains(c)) {
					if(!charList.isEmpty()) {
						batchList.add(Utils.join("", charList));
					}
					batchList.add(String.valueOf(c));
					//清空容器
					charList.clear();
				}else {
					charList.add(chars[i]);
				}
			}
		}
		//处理未处理完的内容
		if(!charList.isEmpty()) {
			batchList.add(Utils.join("", charList));
		}
		/*检查选择器格式*/
		//遍历检查选择器格式
		for(int i=0,len=batchList.size(); i<len; i++) {
			//当前
			String s = batchList.get(i);
			//找到“:”符号
			if(":".equals(s)) {
				try {
					//前面是空格
					if(" ".equals(batchList.get(i-1))) {
						throw new IllegalArgumentException("空格后面不能紧跟“:”符号");
					}
					//下一个符号
					String nx = batchList.get(i+1);
					if(!StringUtils.isExist(nx, "first-child", "last-child", "nth-child")) {
						throw new IllegalArgumentException("“:”符号后必须跟first-child、last-child或nth-child");
					}
					//如果为nth-child
					if("nth-child".equals(nx)) {
						//获取位置
						String place = batchList.get(i+3);
						//place有3种情况，1：纯数字，2：±n+b，3：kn±b（b为0可省略）
						int type = place.matches("^\\d+$") ? 1 : 
								(place.matches("^[+-]?n\\+\\d+$") ? 2 : 
									(place.matches("^\\d+n([+-]\\d+)?$") ? 3 : 0));
						//格式错误
						if(type==0) {
							throw new IllegalArgumentException("“:nth-child(xx)”用法错误，请检查选择器");
						}
						//处理±n+b
						if(type==2) {
							//n的位置
							int n = place.indexOf('n');
							//n开头
							if(n==0) {
								batchList.set(i+3, '1' + place);
							}else {
								batchList.set(i+3, "" + place.charAt(0) + '1' + place.substring(1));
							}
						}
					}
				}catch (IndexOutOfBoundsException e) {
					throw new IllegalArgumentException("“:xxx-chid”用法错误，请检查选择器", e);
				}
			}
		}
		//返回
		return batchList;
	}
	
	
}
